﻿using HmmmIDE.Intellisense;
using System;
using System.Text.RegularExpressions;
using System.Reflection;
using System.Threading;
using HmmmIDE.Syntax;

namespace HmmmIDE.Debug
{
    public class Simulator
    {
        private Instruction[] mInstructions;
        const int MaxRepeat = 1000;
        public delegate void OnCommandExecutedHandler(object sender, SimulatorEventArgs e);
        public delegate void OnSimulationFinishedHandler(object sender, EventArgs e);

        /// <summary>
        /// Fired when single command has been executed
        /// </summary>
        public event OnCommandExecutedHandler CommandExecuted;
        public event OnSimulationFinishedHandler SimulationFinished;
        private Thread runThread;

        public Simulator(Instruction[] instructions)
        {
            this.mInstructions = instructions;
            DebugService.Initialize();
        }

        public readonly AutoResetEvent _ShouldContinue = new AutoResetEvent(false);


        public void Stop()
        {
            if(runThread!= null && runThread.IsAlive)
            {
                // runThread.Abort(); Do not use Abort() unless necessary
                runThread.Interrupt();
            }
        }

        private void RunBackground()
        {
            int repeat = 0;
            try
            {

                MethodManager manager = new MethodManager();
                while (DebugService.PC != -1)
                {
                    if (DebugService.ShouldStop) _ShouldContinue.WaitOne();

                    // get the parameter to invoke method
                    Object[] para = GetParameters(mInstructions[DebugService.PC].Text);

                    string methodName = (string)para[0];
                    Object[] arg = new Object[para.Length - 1];
                    Array.Copy(para, 1, arg, 0, para.Length - 1);

                    Delegate del = manager.GetMethod(methodName);

                    del.DynamicInvoke(arg);

                    repeat++;
                    if (DebugService.PC == mInstructions.Length)
                    {
                        // the user forget to add halt
                        throw new Exception("Program halted. Did you forget to add halt?");
                    }
                         
                    if (repeat == MaxRepeat)
                    {
                        // Most likely it's an infinite loop
                        throw new Exception("Infinite loop reached. Program halted");
                    }

                    if (DebugService.PC != -1)
                        CommandExecuted(this, new SimulatorEventArgs(mInstructions[DebugService.PC].LineNumber, DebugService.PC));
                }


            }
            catch(ThreadInterruptedException)
            {
                _ShouldContinue.Dispose();
            }
            catch(System.OverflowException)
            {
                DebugService.Write("Number Overflow");
            }
            catch(TargetInvocationException)
            {
                DebugService.Write("Number Overflow");
                // some problems here, the overflow exception is caught here
            }
            catch (Exception ex)
            {
                DebugService.Write(ex.Message);
            }
            finally
            {
                SimulationFinished(this, EventArgs.Empty);
            }
            
        }

        /// <summary>
        /// Run simulation at background
        /// </summary>
        public void Run()
        {
            runThread = new Thread(RunBackground);
            runThread.Start();
            
        }

        /// <summary>
        /// Return the parameters based on the instruction
        /// </summary>
        /// <param name="code">instruction code</param>
        /// <returns>Boxed objects list that contains all the parameters including command name as index 0</returns>
        private Object[] GetParameters(string code)
        {

            int count = 0;
            string commandName = "";
            foreach(var kvp in SyntaxManager.CommandRegex)
            {
                if(Regex.IsMatch(code, kvp.Value))
                {
                    count++;
                    commandName = kvp.Key;
                }
            }

            if (count != 1)
                throw new Exception("Two instructions in one line");    // special check
            Regex regex = new Regex(SyntaxManager.CommandRegex[commandName]);
            code = regex.Match(code).Value; // get the instruction
            string[] data = code.Split(new char[] { ',', ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
            Object[] result = new Object[data.Length - 1];  // Subtract the instruction number and command name
            for(int i = 2; i < data.Length; i++)
            {
                if(data[i][0] == 'r')       // it's a register
                {
                    result[i-1] = DebugService.GetRegisterByName(data[i]);
                }
                // it's a number
                else
                {
                    result[i - 1] = Int16.Parse(data[i]);
                }
            }

            result[0] = commandName;

            foreach (Object obj in result) if (obj == null) throw new Exception("Syntax error at " + data[0]);

            return result;
        }

        public class SimulatorEventArgs :EventArgs
        {
            public int LineNumber { get; set; }
            public int PC { get; set; }
            public SimulatorEventArgs(int linenumber, int pc)
            {
                this.LineNumber = linenumber;
                this.PC = pc;
            }
        }
    }
}
